package sweet.mvc.servlet
import javax.servlet.http.{HttpServletRequest=>Req, HttpServletResponse=>Resp, _}
import org.slf4j.{ Logger, LoggerFactory }
import sweet.helper._
import sweet._

class SweetDispatcherServlet extends HttpServlet {
  protected val logger = LoggerFactory.getLogger(getClass)
  
  protected var config: SweetConfig = ApplicationConfig.defaultConfig
  
  //marked lazy will give the config to have chance to load first.
  protected lazy val controllers = config.controllers
  protected lazy val sweetDispatcherName = config.sweetDispatcherName
  protected lazy val viewPrefix = config.viewPrefix
  protected lazy val viewSuffix = config.viewSuffix
  
  override def init{
    logger.debug("Initializing dispatcher.")
    
    //add uri routing path variables into servletContext scope
    val sc = getServletContext
    val contextPath =sc.getContextPath
    val webappName = "/" + getServletName
    val webappPath = contextPath + webappName
    
    logger.debug("Exporting contextPath "+contextPath)
    sc.setAttribute(Constants.CONTEXT_PATH_KEY, contextPath)
    logger.debug("Exporting webappPath "+webappPath)
    sc.setAttribute(Constants.WEBAPP_NAME_KEY, webappName)
    sc.setAttribute(Constants.WEBAPP_PATH_KEY, webappPath)
    
    //load user config instance.
    val configClassName = getInitParameter(Constants.SWEET_CONFIG_CLASSNAME_INIT_PARAM_KEY)
    logger.debug("Custom config class name " + configClassName)
    if(configClassName != null && configClassName != "")
      config = ApplicationConfig.init(Class.forName(configClassName))
    
    //saving dispatcher/webapp name used to route and search for controller and views.  
    logger.debug("Re-assign config.sweetDispatcherName to " + webappName)
    config.sweetDispatcherName = webappName
    
    //run init codes in config instance
    config.init
    
    //adding controller into list
    //TODO: waiting to have a ScalaWrapper for FM to replace this
    val cmap = new JMap[String, JList[String]]
    for((k,v) <- controllers) cmap.put(k, new JList(v.handlers.keys.toList))
    sc.setAttribute(Constants.CONTROLLER_HANDLER_NAMES_KEY, cmap)
  }
  
  override def destroy{
    //run shutdown code in config instance
    config.destroy  
  }
    
  override def doGet(req: Req, resp: Resp) = process(req, resp)
  override def doPost(req: Req, resp: Resp) = process(req, resp)
  
  /* Find matching controller and delegate work to it's instances. */
  def process(req: Req, resp: Resp){
    val uri = req.getRequestURI
    logger.debug("Processing uri " + uri)
    
    //find controller and process request
    val Pair(controllerName, controller) = findController(req)
    val view = controller.process(req, resp)
    
    //do nothing if it's empty view - assume controller handled the response already.
    if(!view.isInstanceOf[EmptyView.type]){
      //add models to request scope and return view
      logger.debug("View returned " + view.name)
      for((k,v) <- view.models){
        logger.debug("Adding view model to request scope "+k+": "+v)
        req.setAttribute(k, v) //add all models into request scope
      }
      //find view name and forward the request to view
      val viewPath = viewPrefix+controllerName+view.name+viewSuffix
      req.getRequestDispatcher(viewPath).forward(req, resp)
    }
  }
  
  /* If no controller is found, NoSuchElementException will be thrown. 
   * The controller name is also returned for conviences. */
  def findController(req: Req): (String, Controller) = {
    val uri = req.getRequestURI
    val contextPath = req.getContextPath
    val webappPath = getServletContext.getAttribute(Constants.WEBAPP_PATH_KEY).asInstanceOf[String]
    val uriTail = uri.substring(webappPath.length)
    val controllerNamePos = uriTail.indexOf('/', 1) //skip first slash pos
    val controllerName = if(controllerNamePos<0) uriTail else uriTail.substring(0, controllerNamePos)
    val controllerPath = webappPath+controllerName
    //logger.debug(">>> " +List(controllerName, controllerPath, uriTail))
    val handlerPath = uriTail.substring(controllerName.length)
    req.setAttribute(Constants.CONTROLLER_NAME_KEY, controllerName)
    logger.debug("Exporting controllerPath " + controllerPath)
    req.setAttribute(Constants.CONTROLLER_PATH_KEY, controllerPath)
    logger.debug("Exporting handlerPath " + handlerPath)
    req.setAttribute(Constants.HANDLER_PATH_KEY, handlerPath)
    logger.debug("Fetching controller " + controllerName)
    (controllerName, controllers(controllerName))  // return matching controller path and it's instance
  }
}
